// // Copyright (c) Brock Allen & Dominick Baier. All rights reserved.
// // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.


// using IdentityServer4.Services;
// using IdentityServer4.Stores;
// using Microsoft.AspNetCore.Http;
// using Microsoft.AspNetCore.Mvc;
// using System.Threading.Tasks;
// using Microsoft.AspNetCore.Authentication;
// using IdentityServer4.Events;
// using IdentityServer4.Models;
// using Microsoft.AspNetCore.Identity;
// using IdentityServer4.Extensions;
// using System.Security.Principal;
// using System.Security.Claims;
// using IdentityModel;
// using System.Linq;
// using System;
// using System.Collections.Generic;
// using Microsoft.AspNetCore.Authorization;
// using AspNetCoreSpa.STS;

// namespace IdentityServer4.Quickstart.UI
// {
//     [SecurityHeaders]
//     public class AccountController : Controller
//     {
//         private readonly UserManager<ApplicationUser> _userManager;
//         private readonly SignInManager<ApplicationUser> _signInManager;
//         private readonly IIdentityServerInteractionService _interaction;
//         private readonly IClientStore _clientStore;
//         private readonly IAuthenticationSchemeProvider _schemeProvider;
//         private readonly IEventService _events;

//         public AccountController(
//             UserManager<ApplicationUser> userManager,
//             SignInManager<ApplicationUser> signInManager,
//             IIdentityServerInteractionService interaction,
//             IClientStore clientStore,
//             IAuthenticationSchemeProvider schemeProvider,
//             IEventService events)
//         {
//             _userManager = userManager;
//             _signInManager = signInManager;
//             _interaction = interaction;
//             _clientStore = clientStore;
//             _schemeProvider = schemeProvider;
//             _events = events;
//         }

//         /// <summary>
//         /// Show login page
//         /// </summary>
//         [HttpGet]
//         public async Task<IActionResult> Login(string returnUrl)
//         {
//             // build a model so we know what to show on the login page
//             var vm = await BuildLoginViewModelAsync(returnUrl);

//             if (vm.IsExternalLoginOnly)
//             {
//                 // we only have one option for logging in and it's an external provider
//                 return await ExternalLogin(vm.ExternalLoginScheme, returnUrl);
//             }

//             return View(vm);
//         }

//         /// <summary>
//         /// Handle postback from username/password login
//         /// </summary>
//         [HttpPost]
//         [ValidateAntiForgeryToken]
//         public async Task<IActionResult> Login(LoginInputModel model, string button)
//         {
//             if (button != "login")
//             {
//                 // the user clicked the "cancel" button
//                 var context = await _interaction.GetAuthorizationContextAsync(model.ReturnUrl);
//                 if (context != null)
//                 {
//                     // if the user cancels, send a result back into IdentityServer as if they 
//                     // denied the consent (even if this client does not require consent).
//                     // this will send back an access denied OIDC error response to the client.
//                     await _interaction.GrantConsentAsync(context, ConsentResponse.Denied);

//                     // we can trust model.ReturnUrl since GetAuthorizationContextAsync returned non-null
//                     return Redirect(model.ReturnUrl);
//                 }
//                 else
//                 {
//                     // since we don't have a valid context, then we just go back to the home page
//                     return Redirect("~/");
//                 }
//             }

//             if (ModelState.IsValid)
//             {
//                 var result = await _signInManager.PasswordSignInAsync(model.Username, model.Password, model.RememberLogin, lockoutOnFailure: true);
//                 if (result.Succeeded)
//                 {
//                     var user = await _userManager.FindByNameAsync(model.Username);
//                     await _events.RaiseAsync(new UserLoginSuccessEvent(user.UserName, user.Id.ToString(), user.UserName));

//                     // make sure the returnUrl is still valid, and if so redirect back to authorize endpoint or a local page
//                     // the IsLocalUrl check is only necessary if you want to support additional local pages, otherwise IsValidReturnUrl is more strict
//                     if (_interaction.IsValidReturnUrl(model.ReturnUrl) || Url.IsLocalUrl(model.ReturnUrl))
//                     {
//                         return Redirect(model.ReturnUrl);
//                     }

//                     return Redirect("~/");
//                 }

//                 await _events.RaiseAsync(new UserLoginFailureEvent(model.Username, "invalid credentials"));

//                 ModelState.AddModelError("", AccountOptions.InvalidCredentialsErrorMessage);
//             }

//             // something went wrong, show form with error
//             var vm = await BuildLoginViewModelAsync(model);
//             return View(vm);
//         }

//         /// <summary>
//         /// initiate roundtrip to external authentication provider
//         /// </summary>
//         [HttpGet]
//         public async Task<IActionResult> ExternalLogin(string provider, string returnUrl)
//         {
//             if (AccountOptions.WindowsAuthenticationSchemeName == provider)
//             {
//                 // windows authentication needs special handling
//                 return await ProcessWindowsLoginAsync(returnUrl);
//             }
//             else
//             {
//                 // start challenge and roundtrip the return URL and 
//                 var props = new AuthenticationProperties()
//                 {
//                     RedirectUri = Url.Action("ExternalLoginCallback"),
//                     Items =
//                     {
//                         { "returnUrl", returnUrl },
//                         { "scheme", provider },
//                     }
//                 };
//                 return Challenge(props, provider);
//             }
//         }

//         /// <summary>
//         /// Post processing of external authentication
//         /// </summary>
//         [HttpGet]
//         public async Task<IActionResult> ExternalLoginCallback()
//         {
//             // read external identity from the temporary cookie
//             var result = await HttpContext.AuthenticateAsync(IdentityServerConstants.ExternalCookieAuthenticationScheme);

//             if (result?.Succeeded != true)
//             {
//                 throw new Exception("External authentication error");
//             }

//             // lookup our user and external provider info
//             var (user, provider, providerUserId, claims) = await FindUserFromExternalProviderAsync(result);
//             if (user == null)
//             {
//                 // this might be where you might initiate a custom workflow for user registration
//                 // in this sample we don't show how that would be done, as our sample implementation
//                 // simply auto-provisions new external user
//                 user = await AutoProvisionUserAsync(provider, providerUserId, claims);
//             }

//             // this allows us to collect any additonal claims or properties
//             // for the specific prtotocols used and store them in the local auth cookie.
//             // this is typically used to store data needed for signout from those protocols.
//             var additionalLocalClaims = new List<Claim>();
//             var localSignInProps = new AuthenticationProperties();
//             ProcessLoginCallbackForOidc(result, additionalLocalClaims, localSignInProps);
//             ProcessLoginCallbackForWsFed(result, additionalLocalClaims, localSignInProps);
//             ProcessLoginCallbackForSaml2p(result, additionalLocalClaims, localSignInProps);

//             // issue authentication cookie for user
//             // we must issue the cookie maually, and can't use the SignInManager because
//             // it doesn't expose an API to issue additional claims from the login workflow
//             var principal = await _signInManager.CreateUserPrincipalAsync(user);
//             additionalLocalClaims.AddRange(principal.Claims);
//             var name = principal.FindFirst(JwtClaimTypes.Name)?.Value ?? user.Id.ToString();
//             await _events.RaiseAsync(new UserLoginSuccessEvent(provider, providerUserId, user.Id.ToString(), name));
//             await HttpContext.SignInAsync(user.Id.ToString(), name, provider, localSignInProps, additionalLocalClaims.ToArray());

//             // delete temporary cookie used during external authentication
//             await HttpContext.SignOutAsync(IdentityServerConstants.ExternalCookieAuthenticationScheme);

//             // validate return URL and redirect back to authorization endpoint or a local page
//             var returnUrl = result.Properties.Items["returnUrl"];
//             if (_interaction.IsValidReturnUrl(returnUrl) || Url.IsLocalUrl(returnUrl))
//             {
//                 return Redirect(returnUrl);
//             }

//             return Redirect("~/");
//         }

//         /// <summary>
//         /// Show logout page
//         /// </summary>
//         [HttpGet]
//         public async Task<IActionResult> Logout(string logoutId)
//         {
//             // build a model so the logout page knows what to display
//             var vm = await BuildLogoutViewModelAsync(logoutId);

//             if (vm.ShowLogoutPrompt == false)
//             {
//                 // if the request for logout was properly authenticated from IdentityServer, then
//                 // we don't need to show the prompt and can just log the user out directly.
//                 return await Logout(vm);
//             }

//             return View(vm);
//         }

//         /// <summary>
//         /// Handle logout page postback
//         /// </summary>
//         [HttpPost]
//         [ValidateAntiForgeryToken]
//         public async Task<IActionResult> Logout(LogoutInputModel model)
//         {
//             // build a model so the logged out page knows what to display
//             var vm = await BuildLoggedOutViewModelAsync(model.LogoutId);

//             if (User?.Identity.IsAuthenticated == true)
//             {
//                 // delete local authentication cookie
//                 await _signInManager.SignOutAsync();

//                 // raise the logout event
//                 await _events.RaiseAsync(new UserLogoutSuccessEvent(User.GetSubjectId(), User.GetDisplayName()));
//             }

//             // check if we need to trigger sign-out at an upstream identity provider
//             if (vm.TriggerExternalSignout)
//             {
//                 // build a return URL so the upstream provider will redirect back
//                 // to us after the user has logged out. this allows us to then
//                 // complete our single sign-out processing.
//                 string url = Url.Action("Logout", new { logoutId = vm.LogoutId });

//                 // this triggers a redirect to the external provider for sign-out
//                 return SignOut(new AuthenticationProperties { RedirectUri = url }, vm.ExternalAuthenticationScheme);
//             }

//             if (string.IsNullOrWhiteSpace(vm.PostLogoutRedirectUri))
//             {
//                 return View("LoggedOut", vm);
//             }
//             else
//             {
//                 return Redirect(vm.PostLogoutRedirectUri);
//             }
//         }

//         /*****************************************/
//         /* helper APIs for the AccountController */
//         /*****************************************/
//         private async Task<LoginViewModel> BuildLoginViewModelAsync(string returnUrl)
//         {
//             var context = await _interaction.GetAuthorizationContextAsync(returnUrl);
//             if (context?.IdP != null)
//             {
//                 // this is meant to short circuit the UI and only trigger the one external IdP
//                 return new LoginViewModel
//                 {
//                     EnableLocalLogin = false,
//                     ReturnUrl = returnUrl,
//                     Username = context?.LoginHint,
//                     ExternalProviders = new ExternalProvider[] { new ExternalProvider { AuthenticationScheme = context.IdP } }
//                 };
//             }

//             var schemes = await _schemeProvider.GetAllSchemesAsync();

//             var providers = schemes
//                 .Where(x => x.DisplayName != null ||
//                             (x.Name.Equals(AccountOptions.WindowsAuthenticationSchemeName, StringComparison.OrdinalIgnoreCase))
//                 )
//                 .Select(x => new ExternalProvider
//                 {
//                     DisplayName = x.DisplayName,
//                     AuthenticationScheme = x.Name
//                 }).ToList();

//             var allowLocal = true;
//             if (context?.ClientId != null)
//             {
//                 var client = await _clientStore.FindEnabledClientByIdAsync(context.ClientId);
//                 if (client != null)
//                 {
//                     allowLocal = client.EnableLocalLogin;

//                     if (client.IdentityProviderRestrictions != null && client.IdentityProviderRestrictions.Any())
//                     {
//                         providers = providers.Where(provider => client.IdentityProviderRestrictions.Contains(provider.AuthenticationScheme)).ToList();
//                     }
//                 }
//             }

//             return new LoginViewModel
//             {
//                 AllowRememberLogin = AccountOptions.AllowRememberLogin,
//                 EnableLocalLogin = allowLocal && AccountOptions.AllowLocalLogin,
//                 ReturnUrl = returnUrl,
//                 Username = context?.LoginHint,
//                 ExternalProviders = providers.ToArray()
//             };
//         }

//         private async Task<LoginViewModel> BuildLoginViewModelAsync(LoginInputModel model)
//         {
//             var vm = await BuildLoginViewModelAsync(model.ReturnUrl);
//             vm.Username = model.Username;
//             vm.RememberLogin = model.RememberLogin;
//             return vm;
//         }

//         private async Task<LogoutViewModel> BuildLogoutViewModelAsync(string logoutId)
//         {
//             var vm = new LogoutViewModel { LogoutId = logoutId, ShowLogoutPrompt = AccountOptions.ShowLogoutPrompt };

//             if (User?.Identity.IsAuthenticated != true)
//             {
//                 // if the user is not authenticated, then just show logged out page
//                 vm.ShowLogoutPrompt = false;
//                 return vm;
//             }

//             var context = await _interaction.GetLogoutContextAsync(logoutId);
//             if (context?.ShowSignoutPrompt == false)
//             {
//                 // it's safe to automatically sign-out
//                 vm.ShowLogoutPrompt = false;
//                 return vm;
//             }

//             // show the logout prompt. this prevents attacks where the user
//             // is automatically signed out by another malicious web page.
//             return vm;
//         }

//         private async Task<LoggedOutViewModel> BuildLoggedOutViewModelAsync(string logoutId)
//         {
//             // get context information (client name, post logout redirect URI and iframe for federated signout)
//             var logout = await _interaction.GetLogoutContextAsync(logoutId);

//             var vm = new LoggedOutViewModel
//             {
//                 AutomaticRedirectAfterSignOut = AccountOptions.AutomaticRedirectAfterSignOut,
//                 PostLogoutRedirectUri = logout?.PostLogoutRedirectUri,
//                 ClientName = string.IsNullOrEmpty(logout?.ClientName) ? logout?.ClientId : logout?.ClientName,
//                 SignOutIframeUrl = logout?.SignOutIFrameUrl,
//                 LogoutId = logoutId
//             };

//             if (User?.Identity.IsAuthenticated == true)
//             {
//                 var idp = User.FindFirst(JwtClaimTypes.IdentityProvider)?.Value;
//                 if (idp != null && idp != IdentityServer4.IdentityServerConstants.LocalIdentityProvider)
//                 {
//                     var providerSupportsSignout = await HttpContext.GetSchemeSupportsSignOutAsync(idp);
//                     if (providerSupportsSignout)
//                     {
//                         if (vm.LogoutId == null)
//                         {
//                             // if there's no current logout context, we need to create one
//                             // this captures necessary info from the current logged in user
//                             // before we signout and redirect away to the external IdP for signout
//                             vm.LogoutId = await _interaction.CreateLogoutContextAsync();
//                         }

//                         vm.ExternalAuthenticationScheme = idp;
//                     }
//                 }
//             }

//             return vm;
//         }

//         private async Task<IActionResult> ProcessWindowsLoginAsync(string returnUrl)
//         {
//             // see if windows auth has already been requested and succeeded
//             var result = await HttpContext.AuthenticateAsync(AccountOptions.WindowsAuthenticationSchemeName);
//             if (result?.Principal is WindowsPrincipal wp)
//             {
//                 // we will issue the external cookie and then redirect the
//                 // user back to the external callback, in essence, tresting windows
//                 // auth the same as any other external authentication mechanism
//                 var props = new AuthenticationProperties()
//                 {
//                     RedirectUri = Url.Action("ExternalLoginCallback"),
//                     Items =
//                     {
//                         { "returnUrl", returnUrl },
//                         { "scheme", AccountOptions.WindowsAuthenticationSchemeName },
//                     }
//                 };

//                 var id = new ClaimsIdentity(AccountOptions.WindowsAuthenticationSchemeName);
//                 id.AddClaim(new Claim(JwtClaimTypes.Subject, wp.Identity.Name));
//                 id.AddClaim(new Claim(JwtClaimTypes.Name, wp.Identity.Name));

//                 // add the groups as claims -- be careful if the number of groups is too large
//                 if (AccountOptions.IncludeWindowsGroups)
//                 {
//                     var wi = wp.Identity as WindowsIdentity;
//                     var groups = wi.Groups.Translate(typeof(NTAccount));
//                     var roles = groups.Select(x => new Claim(JwtClaimTypes.Role, x.Value));
//                     id.AddClaims(roles);
//                 }

//                 await HttpContext.SignInAsync(
//                     IdentityServer4.IdentityServerConstants.ExternalCookieAuthenticationScheme,
//                     new ClaimsPrincipal(id),
//                     props);
//                 return Redirect(props.RedirectUri);
//             }
//             else
//             {
//                 // trigger windows auth
//                 // since windows auth don't support the redirect uri,
//                 // this URL is re-triggered when we call challenge
//                 return Challenge(AccountOptions.WindowsAuthenticationSchemeName);
//             }
//         }

//         private async Task<(ApplicationUser user, string provider, string providerUserId, IEnumerable<Claim> claims)>
//             FindUserFromExternalProviderAsync(AuthenticateResult result)
//         {
//             var externalUser = result.Principal;

//             // try to determine the unique id of the external user (issued by the provider)
//             // the most common claim type for that are the sub claim and the NameIdentifier
//             // depending on the external provider, some other claim type might be used
//             var userIdClaim = externalUser.FindFirst(JwtClaimTypes.Subject) ??
//                               externalUser.FindFirst(ClaimTypes.NameIdentifier) ??
//                               throw new Exception("Unknown userid");

//             // remove the user id claim so we don't include it as an extra claim if/when we provision the user
//             var claims = externalUser.Claims.ToList();
//             claims.Remove(userIdClaim);

//             var provider = result.Properties.Items["scheme"];
//             var providerUserId = userIdClaim.Value;

//             // find external user
//             var user = await _userManager.FindByLoginAsync(provider, providerUserId);

//             return (user, provider, providerUserId, claims);
//         }

//         private async Task<ApplicationUser> AutoProvisionUserAsync(string provider, string providerUserId, IEnumerable<Claim> claims)
//         {
//             // create a list of claims that we want to transfer into our store
//             var filtered = new List<Claim>();

//             // user's display name
//             var name = claims.FirstOrDefault(x => x.Type == JwtClaimTypes.Name)?.Value ??
//                 claims.FirstOrDefault(x => x.Type == ClaimTypes.Name)?.Value;
//             if (name != null)
//             {
//                 filtered.Add(new Claim(JwtClaimTypes.Name, name));
//             }
//             else
//             {
//                 var first = claims.FirstOrDefault(x => x.Type == JwtClaimTypes.GivenName)?.Value ??
//                     claims.FirstOrDefault(x => x.Type == ClaimTypes.GivenName)?.Value;
//                 var last = claims.FirstOrDefault(x => x.Type == JwtClaimTypes.FamilyName)?.Value ??
//                     claims.FirstOrDefault(x => x.Type == ClaimTypes.Surname)?.Value;
//                 if (first != null && last != null)
//                 {
//                     filtered.Add(new Claim(JwtClaimTypes.Name, first + " " + last));
//                 }
//                 else if (first != null)
//                 {
//                     filtered.Add(new Claim(JwtClaimTypes.Name, first));
//                 }
//                 else if (last != null)
//                 {
//                     filtered.Add(new Claim(JwtClaimTypes.Name, last));
//                 }
//             }

//             // email
//             var email = claims.FirstOrDefault(x => x.Type == JwtClaimTypes.Email)?.Value ??
//                claims.FirstOrDefault(x => x.Type == ClaimTypes.Email)?.Value;
//             if (email != null)
//             {
//                 filtered.Add(new Claim(JwtClaimTypes.Email, email));
//             }

//             var user = new ApplicationUser
//             {
//                 UserName = Guid.NewGuid().ToString(),
//             };
//             var identityResult = await _userManager.CreateAsync(user);
//             if (!identityResult.Succeeded) throw new Exception(identityResult.Errors.First().Description);

//             if (filtered.Any())
//             {
//                 identityResult = await _userManager.AddClaimsAsync(user, filtered);
//                 if (!identityResult.Succeeded) throw new Exception(identityResult.Errors.First().Description);
//             }

//             identityResult = await _userManager.AddLoginAsync(user, new UserLoginInfo(provider, providerUserId, provider));
//             if (!identityResult.Succeeded) throw new Exception(identityResult.Errors.First().Description);

//             return user;
//         }

//         private void ProcessLoginCallbackForOidc(AuthenticateResult externalResult, List<Claim> localClaims, AuthenticationProperties localSignInProps)
//         {
//             // if the external system sent a session id claim, copy it over
//             // so we can use it for single sign-out
//             var sid = externalResult.Principal.Claims.FirstOrDefault(x => x.Type == JwtClaimTypes.SessionId);
//             if (sid != null)
//             {
//                 localClaims.Add(new Claim(JwtClaimTypes.SessionId, sid.Value));
//             }

//             // if the external provider issued an id_token, we'll keep it for signout
//             var id_token = externalResult.Properties.GetTokenValue("id_token");
//             if (id_token != null)
//             {
//                 localSignInProps.StoreTokens(new[] { new AuthenticationToken { Name = "id_token", Value = id_token } });
//             }
//         }

//         private void ProcessLoginCallbackForWsFed(AuthenticateResult externalResult, List<Claim> localClaims, AuthenticationProperties localSignInProps)
//         {
//         }

//         private void ProcessLoginCallbackForSaml2p(AuthenticateResult externalResult, List<Claim> localClaims, AuthenticationProperties localSignInProps)
//         {
//         }

//         [HttpGet]
//         [AllowAnonymous]
//         public IActionResult Register(string returnUrl = null)
//         {
//             ViewData["ReturnUrl"] = returnUrl;
//             return View();
//         }

//         [HttpPost]
//         [AllowAnonymous]
//         [ValidateAntiForgeryToken]
//         public async Task<IActionResult> Register(RegisterViewModel model, string button, string returnUrl = null)
//         {
//             if (button != "register")
//             {
//                 return Redirect(returnUrl);
//             }

//             ViewData["ReturnUrl"] = returnUrl;
//             if (ModelState.IsValid)
//             {
//                 var user = new ApplicationUser { UserName = model.Email, Email = model.Email };
//                 var result = await _userManager.CreateAsync(user, model.Password);
//                 if (result.Succeeded)
//                 {
//                     await _signInManager.SignInAsync(user, isPersistent: false);
//                     return Redirect(returnUrl);
//                 }
//                 else
//                 {
//                     ModelState.AddModelError("", "Unable to create an account, please try again later.");
//                     return View(result.Errors);
//                 }
//             }

//             var registerVm = new RegisterViewModel
//             {
//                 Email = model.Email
//             };

//             return View(registerVm);
//         }

//     }
// }